<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- <script>
        //主题
        class Dep {
            constructor(callback) {
                this.subs = [];
                //每个主题更新数据的方法不一样，所以需要传进来
                this.callback = callback;
            }
            addSub(sub) {
                this.subs.push(sub);
            }
            notify() {
                //通知此主题所有订阅者更新数据
                this.subs.forEach(item => item.update(this.callback))
            }
        }
        //订阅者（Watcher）
        class Sub {
            constructor(val) {
                this.val = val
            }
            update(callback) {
                this.val = callback(this.val);
                //相当于更新视图操作
                console.log(this.val);

            }
        }
        //发布者
        class Pub {
            constructor() {
                this.deps = [];
            }
            addDep(dep) {
                this.deps.push(dep)
            }
            removeDep(dep) {
                let index = this.deps.indexOf(dep);
                if (index !== -1) {
                    this.deps.splice(index, 1);
                    return true
                } else {
                    return false
                }
            }
            publish(dep) {
                //发布某个主题的更新
                this.deps.forEach(item => item == dep && item.notify());
            }
        }

        //新建主题， 并向主题中增加订阅者
        let dep1 = new Dep(item => item * item);
        //(param1, param2, …, paramN) => { statements } √√
        //(param1, param2, …, paramN) => expression
        //相当于：(param1, param2, …, paramN) =>{ return expression; }
        // dep1.addSub(new Sub(1)).addSub(new Sub(2)).addSub(new Sub(3));
        dep1.addSub(new Sub(1));
        dep1.addSub(new Sub(2));
        dep1.addSub(new Sub(3));
        //新建发布者， 并向发布者中增加主题
        let pub = new Pub();
        pub.addDep(dep1);
        //发布者发布， 通知所有主题的所有订阅者更新
        pub.publish(dep1);
        //发布者发布， 通知所有主题的所有订阅者更新
        pub.publish(dep1);
        console.log("===========================");
        //新建主题， 并向主题中增加订阅者
        let dep2 = new Dep(item => item + item);
        dep2.addSub(new Sub(1));
        dep2.addSub(new Sub(2));
        dep2.addSub(new Sub(3));
        //向发布者中增加主题
        pub.addDep(dep2);
        //发布者发布， 通知所有主题的所有订阅者更新
        pub.publish(dep2);
        //发布者发布， 通知所有主题的所有订阅者更新
        pub.publish(dep2);
    </script> -->
    <!-- <script>
        class EventEmitter {
            constructor() {
                this._events = Object.create(null) //创建一个新对象来存放所有被监听的事件和处理函数
                console.log(this._events);

            }

            on(event, callback) { //监听event事件，触发时调用callback函数
                let callbacks = this._events[event] || [] //判断event事件是否已有其他事件处理函数，没有则为空数组
                callbacks.push(callback)
                this._events[event] = callbacks
                return this
            }
            off(event, callback) { //停止监听event事件
                let callbacks = this._events[event]
                this._events[event] = callbacks && callbacks.filter(fn => fn !== callback) //如果event事件内存在事件处理函数，就查找其中是否有callback函数并把它过滤掉。
                return this
            }
            emit(...args) { //触发事件，并把参数传给事件的处理函数
                const event = args[0]
                const params = [].slice.call(args, 1) //!!!因为args非数组，所以需要通过空数组来调用数组的slice方法并把this指向args
                const callbacks = this._events[event] //获取event事件的全部事件处理函数
                callbacks.forEach(fn => fn.apply(this, params)) //遍历执行全部事件处理函数
                return this
            }
            once(event, callback) { //为事件注册单次监听器
                let wrapFanc = (...args) => { //创建一个wrapFanc函数实现单次调用后停止监听
                    callback.apply(this, args) //执行wrapFanc
                    this.off(event, wrapFanc) //后停止监听事件
                }
                this.on(event, wrapFanc) //注册监听wrapFanc事件
                return this
            }
        }

        const emitter = new EventEmitter();
        emitter.on('drink', (person) => {
            console.log(person + '喝水')
        })
        emitter.on('drink', (person) => {
            console.log(person + '喝水2')
        })
        emitter.on('eat', (person) => {
            console.log(person + '吃东西')
        })
        emitter.on('eat', (person) => {
            console.log(person + '吃东西2')
        })
        emitter.once('buy', (person) => {
            console.log(person + '买东西')
        })
        emitter.emit('drink', '我') // 我喝水
        emitter.emit('drink', '我') // 我喝水
        emitter.emit('eat', '其它人') // 其它人吃东西
        emitter.emit('eat', '其它人') // 其它人吃东西
        emitter.emit('buy', '其它人') //其它人买东西
        emitter.emit('buy', '其它人') //这里不会再次触发buy事件，因为once只能触发一次
        emitter.emit('buy', '其它人') //这里不会再次触发buy事件，因为once只能触发一次
        emitter.off('drink') //移除eat事件
        emitter.emit('drink', '张铂雨') //这里不会触发eat事件，因为已经移除了
    </script> -->
    <script>
        class EventEmitter {
            // 缓存列表
            constructor() {
                this.list = {}
            }

            // 订阅
            on(event, fn) {
                    let that = this;
                    // 如果对象中没有对应的 event 值，也就是说明没有订阅过，就给 event 创建个缓存列表
                    // 如有对象中有相应的 event 值，把 fn 添加到对应 event 的缓存列表里
                    (that.list[event] || (that.list[event] = [])).push(fn);
                    return that;
                }
                // 监听一次
            once(event, fn) {
                    // 先绑定，调用后删除
                    let that = this;

                    function on() {
                        that.off(event, on);
                        fn.apply(that, arguments);
                    }
                    that.on(event, on);
                    return that;
                }
                // 取消订阅
            off(event, fn) {
                    let that = this;
                    let fns = that.list[event];
                    // 若有 fn，遍历缓存列表，看看传入的 fn 与哪个函数相同，如果相同就直接从缓存列表中删掉即可
                    for (let i = 0; i < fns.length; i++) {
                        if (fns[i] === fn) {
                            fns.splice(i, 1);
                            break;
                        }
                    }
                    return that;
                }
                // 发布
            emit() {
                let that = this;
                // 第一个参数是对应的 event 值，直接用数组的 shift 方法取出
                let event = [].shift.call(arguments),
                    fns = that.list[event];
                // 遍历 event 值对应的缓存列表，依次执行 fn
                fns.forEach(fn => {
                    fn.apply(that, arguments);
                });
                return that;
            }
        };

        function user1(content) {
            console.log('用户1订阅了:', content);
        }

        function user2(content) {
            console.log('用户2订阅了:', content);
        }

        function user3(content) {
            console.log('用户3订阅了:', content);
        }

        function user4(content) {
            console.log('用户4订阅了:', content);
        }
        let eventEmitter = new EventEmitter();
        // 订阅
        eventEmitter.on('article1', user1);
        eventEmitter.on('article1', user2);
        eventEmitter.on('article1', user3);
        // 取消user2方法的订阅
        // eventEmitter.off('article1', user2);

        eventEmitter.once('article2', user4)

        // 发布
        eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
        eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
        eventEmitter.emit('article2', 'Javascript 观察者模式');
        eventEmitter.emit('article2', 'Javascript 观察者模式');

        // eventEmitter.on('article1', user3).emit('article1', 'test111');

        /*
            用户1订阅了: Javascript 发布-订阅模式
            用户3订阅了: Javascript 发布-订阅模式
            用户1订阅了: Javascript 发布-订阅模式
            用户3订阅了: Javascript 发布-订阅模式
            用户4订阅了: Javascript 观察者模式
        */
    </script>
</body>

</html>